/*
 * Copyright (C) 2009 Christoph Caks
 *
 * This file is part of GLWAK.
 *
 * GLWAK is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * GLWAK is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with GLWAK.  If not, see <http://www.gnu.org/licenses/>.
 *
 *
 * X11Window.cpp
 *
 *  Created on: 25.06.2009
 *      Author: Christoph Caks
 */

#include "x11/X11Window.h"

#include <iostream>

#ifndef LOG_DEBUG
#define LOG_DEBUG std::cout
#define LOG_DEBUG1 std::cout
#define LOG_DEBUG2 std::cout
#define LOG_DEBUG3 std::cout
#define LOG_DEBUG4 std::cout
#define LOG_ERROR std::cerr
#define LOG_INFO std::cout
#endif

#define GLERR { \
	int error = glGetError(); \
	if ( !error == GL_NO_ERROR ) { \
		LOG_ERROR << "OpenGL Error occurred. Error Code = " << error; \
	} \
	else { \
		LOG_DEBUG4 << " -> No OpenGL error occurred"; \
	} \
}

namespace glwak {

namespace priv {

X11Window::X11Window() : Window() {
}

X11Window::~X11Window() {
}

size_t X11Window::getWindowHandle() {
	return (size_t) window;
}

void X11Window::initializeImpl() {

	display = XOpenDisplay( 0 );
	if ( display == NULL ) {
		throw "error opening display";
	}

	initVideoModes();

	int HEIGHT = this->configuration.getHeight();
	int WIDTH = this->configuration.getWidth();

	static int visual_attribs[] = {
			GLX_RENDER_TYPE,	GLX_RGBA_BIT,
			GLX_DRAWABLE_TYPE,	GLX_WINDOW_BIT,
			GLX_DOUBLEBUFFER,	true,
			GLX_RED_SIZE,		1,
			GLX_GREEN_SIZE,		1,
			GLX_BLUE_SIZE,		1,
			None
	};

	int fbCount;
	GLXFBConfig *fbConfig = glXChooseFBConfig( display, DefaultScreen( display ), visual_attribs, &fbCount );
	if ( ! fbConfig ) {
		throw "failed to retrieve a framebuffer config";
	}

	XVisualInfo *visualInfo = glXGetVisualFromFBConfig( display, fbConfig[0] );
	if ( visualInfo == NULL ) {
		throw "error choosing visual";
	}

	XSetWindowAttributes swa;

	swa.colormap = XCreateColormap( display, RootWindow( display, visualInfo->screen ), visualInfo->visual, AllocNone );
	swa.border_pixel = 0;
	swa.event_mask = StructureNotifyMask;

	//LOG_INFO << "creating window\n";
	window = XCreateWindow( display, RootWindow( display, visualInfo->screen ), 0, 0, WIDTH,
	                        HEIGHT, 0, visualInfo->depth, InputOutput, visualInfo->visual,
	                        CWBorderPixel | CWColormap | CWEventMask, &swa );
	if ( ! window ) {
		throw "error creating window";
	}

	this->reinitializeImpl();
}

void X11Window::reinitializeImpl() {
	if ( configuration.isFullscreen() ) {
		// FULLSCREEN
		this->setVideoMode( this->findModeForResolution( configuration.getWidth(), configuration.getHeight() ) );

		this->fullscreen();

	}
	else {
		// NO FULLSCREEN
		this->restoreVideoMode();

		this->restore();

	}
}

void X11Window::doMessageLoop( X11Window* ctx ) {
	std::cout << "[X11Window] begin message loop" << std::endl;
	XSelectInput( ctx->display, ctx->window, ResizeRedirectMask | ExposureMask | KeyPressMask | ButtonPressMask);
	XEvent report;
	while ( 1 ) {
		std::cout << "while..." << std::endl;
		XNextEvent( ctx->display, &report );
		std::cout << "got event " << std::endl;
		if( report.type == ResizeRequest ) {
			XResizeRequestEvent* rr = (XResizeRequestEvent*) &report;
			//wrong XResizeWindow( this->display, this->window, rr->width, rr->height );
			ctx->dispatchOnResize( rr->width, rr->height );
		}
		usleep( 100 );
	}
}

void X11Window::startMessageLoop() {
}

void X11Window::startMessageThread() {
	// TODO start a thread
	this->initialize();

	boost::thread *thread = new boost::thread( X11Window::doMessageLoop, this);


	//MessageThread* t = new MessageThread( this );
	//t->start();
}

void X11Window::show() {
	//LOG_INFO << "mapping window\n";
	XMapWindow( display, window );
	XFlush( display );
}

void X11Window::hide() {
	//LOG_INFO << "unmapping window\n";
	XUnmapWindow( display, window );
	XFlush( display );
}

void X11Window::dispose() {
	XFree( modes );
	XDestroyWindow( display, window );
}

Display *X11Window::getDisplay() {
	return this->display;
}

::Window X11Window::getWindow() {
	return this->window;
}

void X11Window::initVideoModes() {
	LOG_DEBUG4 << "[X11Window] initVideoModes() - begin";
	int vmMajor, vmMinor;
	XF86VidModeQueryVersion( display, &vmMajor, &vmMinor );

	LOG_DEBUG << "[X11Window] initVideoModes() - XF86VidMode version " << vmMajor << "." << vmMinor;
	XF86VidModeGetAllModeLines( display, DefaultScreen( display ), &modeNum, &modes );
	LOG_DEBUG4 << "[X11Window] initVideoModes() - end";
}

XF86VidModeModeInfo *X11Window::findModeForResolution( int xres, int yres ) {
	int bestMode = 0;
	for ( int i = 0; i < modeNum; i++ ) {
		if ( ( modes[i]->hdisplay == xres ) && ( modes[i]->vdisplay == yres ) ) {
			bestMode = i;
		}
	}
	return modes[bestMode];
}

void X11Window::setVideoMode( XF86VidModeModeInfo *mode ) {
	LOG_DEBUG4 << "[X11Window] setVideoMode( " << mode->hdisplay << "x" << mode->vdisplay << " ) - begin";
	XF86VidModeSwitchToMode( display, DefaultScreen( display ), mode );
	XF86VidModeSetViewPort( display, DefaultScreen( display ), 0, 0 );
	LOG_DEBUG4 << "[X11Window] setVideoMode( " << mode->hdisplay << "x" << mode->vdisplay << " ) - end";
}

void X11Window::restoreVideoMode() {
	LOG_DEBUG4 << "[X11Window] restoreVideoMode() - begin";
	XF86VidModeSwitchToMode( display, DefaultScreen( display ), modes[0] );
	XF86VidModeSetViewPort( display, DefaultScreen( display ), 0, 0 );
	LOG_DEBUG4 << "[X11Window] restoreVideoMode() - end";
}

int logErrorHandler( Display *dpy, XErrorEvent *e )
{
    char errorText[1024];
    XGetErrorText( dpy, e->error_code, errorText, sizeof(errorText) );
    LOG_ERROR << "**********************************";
    LOG_ERROR << "X Error: " << errorText;
    LOG_ERROR << "**********************************";
}

void X11Window::fullscreen() {
	LOG_DEBUG4 << "[X11Window] fullscreen() - begin";

	XSetErrorHandler( logErrorHandler );

	Atom wmState = XInternAtom( this->display, "_NET_WM_STATE", true );

	int _NET_WM_STATE_REMOVE	=0;
	int _NET_WM_STATE_ADD		=1;
	int _NET_WM_STATE_TOGGLE	=2;

	Atom fullscreen = XInternAtom( this->display, "_NET_WM_STATE_FULLSCREEN", true );

	XClientMessageEvent *event = new XClientMessageEvent();
	event->type = ClientMessage;
	event->window = this->window;
	event->message_type = wmState;
	event->format = 32;
	event->data.l[0] = _NET_WM_STATE_ADD;
	event->data.l[1] = fullscreen;
	event->data.l[2] = 0;
	event->data.l[3] = 0;
	event->data.l[4] = 0;

	Status s = XSendEvent( this->display, this->window, true, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent*) event );
	LOG_DEBUG4 << "[X11Window] XSendEvent() returned " << s;
	LOG_DEBUG4 << "[X11Window] fullscreen() - done";
}

void X11Window::restore() {
	LOG_DEBUG4 << "[X11Window] restore() - begin";

	XSetErrorHandler( logErrorHandler );

	Atom wmState = XInternAtom( this->display, "_NET_WM_STATE", true );

	LOG_INFO << "[X11Window] _NET_WM_STATE atom is " << wmState;

	int _NET_WM_STATE_REMOVE	=0;
	int _NET_WM_STATE_ADD		=1;
	int _NET_WM_STATE_TOGGLE	=2;

	Atom fullscreen = XInternAtom( this->display, "_NET_WM_STATE_FULLSCREEN", true );

	LOG_INFO << "[X11Window] _NET_WM_STATE_FULLSCREEN atom is " << fullscreen;

	XClientMessageEvent *event = new XClientMessageEvent();
	event->type = ClientMessage;
	event->window = this->window;
	event->message_type = wmState;
	event->format = 32;
	event->data.l[0] = _NET_WM_STATE_REMOVE;
	event->data.l[1] = fullscreen;
	event->data.l[2] = 0;
	event->data.l[3] = 0;
	event->data.l[4] = 0;

	Status s = XSendEvent( this->display, this->window, true, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent*) event );

	LOG_DEBUG4 << "[X11Window] XSendEvent() returned " << s;
	LOG_DEBUG4 << "[X11Window] restore() - done";

}



} // namespace priv

} // namespace glwak
